package com.googlecode.javarpctp.remote;

import com.googlecode.javarpctp.communication.ResponseMessage;
import com.googlecode.javarpctp.test.common.ThreadCreatedEvent;
import com.googlecode.javarpctp.test.common.ThreadListener;
import com.googlecode.javarpctp.communication.ConnectionHandler;
import com.googlecode.javarpctp.test.client.Main;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import javax.swing.event.EventListenerList;

public class RemoteDispatcher {

    public final Map<Class<?>, Object> implementations = new HashMap<Class<?>, Object>();
    protected EventListenerList listenerList = new EventListenerList();

    public void registerImplementation(Class<?> interfaceClass, Object implementation) {
        synchronized (this.implementations) {
            this.implementations.put(interfaceClass, implementation);
        }
    }

    public <T extends RemoteService> T getServerCallService(Class<T> classInterface, final ConnectionHandler connectionHandler) {
        @SuppressWarnings("unchecked")
        T service = (T) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{classInterface}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return connectionHandler.sendMessageAndWaitResponse(new CallMessage(new InvocationData(new MethodSignature(method), args)));
            }
        });
        return service;
    }

    public <T extends RemoteService> T getServerExecuteService(Class<T> classInterface, final ConnectionHandler connectionHandler) {
        @SuppressWarnings("unchecked")
        T service = (T) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{classInterface}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                connectionHandler.sendMessage(new ExecuteMessage(new InvocationData(new MethodSignature(method), args)));
                return null;
            }
        });
        return service;
    }

    public void invoke(InvocationResponsiveMessage message, final ConnectionHandler connectionHandler) throws ClassNotFoundException, SecurityException, NoSuchMethodException {        
        final InvocationResponsiveMessage invocationResponsiceMessage = (InvocationResponsiveMessage) message;
        final InvocationData invocationData = invocationResponsiceMessage.getInvocationData();
        final Method method = invocationData.getMethodSignature().resolveMethod();
        final Object[] args = invocationData.getArgs();
        final Object implObj = this.implementations.get(method.getDeclaringClass());

        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                if (invocationResponsiceMessage instanceof CallMessage) {
                    try {
                        ResponseMessage responseMessage;
                        try {
                            Object response = method.invoke(implObj, args);
                            responseMessage = new ResponseMessage(invocationResponsiceMessage.getMessageID(), response);
                        } catch (Exception ex) {
                            responseMessage = new ResponseMessage(invocationResponsiceMessage.getMessageID(), ex);
                        }
                        connectionHandler.sendMessage(responseMessage);
                    } catch (Exception ex) {
                        // Do nothing
                    }
                } else if (invocationResponsiceMessage instanceof ExecuteMessage) {
                    try {
                        method.invoke(implObj, args);
                    } catch (Exception ex) {
                        // Do nothing
                    }
                }
            }
        }, invocationResponsiceMessage.toString());
        this.fireThreadCreated(new ThreadCreatedEvent(this, thread));
        thread.start();
    }

    public void addThreadListener(ThreadListener listener) {
        this.listenerList.add(ThreadListener.class, listener);
    }

    public void removeThreadListener(ThreadListener listener) {
        this.listenerList.remove(ThreadListener.class, listener);
    }

    void fireThreadCreated(ThreadCreatedEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ThreadListener.class) {
                ((ThreadListener) listeners[i + 1]).threadCreated(evt);
            }
        }
    }
}
